home *** CD-ROM | disk | FTP | other *** search
/ Cream of the Crop 1 / Cream of the Crop 1.iso / UTILITY / RSORT.ARJ / RPTAB.PAS < prev   
Pascal/Delphi Source File  |  1991-08-16  |  21KB  |  533 lines

  1.  
  2. {$A+,B-,D-,E-,F-,G-,I-,L-,N-,O-,R-,S-,V-,X-}
  3.  
  4. program RPTab;
  5.  
  6.  
  7. {-------------------------Syntax Of RPTAB ----------------------------------}
  8.  
  9. { RPTAB input-filespec  output-filespec  [tabstop...]
  10.  
  11.  The input is a file containing tabs to be expanded.  The contents of the
  12.  output file will be the same except that all tabs will have been expanded
  13.  to the appropriate number of spaces.
  14.  
  15.  If you don't specify any tab stops, the default tab stops are at columns
  16.  1, 9, 17, 25, 33 and so on at intervals of 8 columns.  If you specify tab
  17.  stops, they must be a sequence of integers each greater than the preceding
  18.  one.  The first tab stop is always at column 1 and you need not specify it.
  19.  RPTAB follows the rule that the interval between the last two tab stops,
  20.  you specify, implies subsequent tab stops at the same interval.  For
  21.  example, the command:
  22.  
  23.     RPTAB  MYTABS.DAT  MYSPACES.DAT  6 15 27
  24.  
  25.  tells RPTAB that the tab stops are at columns 1, 6, 15, 27, 39, 51 and etc.
  26.  The interval of 12 between 15 and 27 is propagated to subsequent tab stops.}
  27.  
  28.  
  29.  {-------------- Const, Type and Variable Declarations ---------------------}
  30.  
  31.   const
  32.     BuffSize = 32768;
  33.  
  34.   type
  35.     TabArray = array[1..50] of Word;
  36.     DataArray = array[0..BuffSize-1] of Char;
  37.     DataPtr = ^DataArray;
  38.  
  39.   var
  40.     Tab : TabArray;         {This array holds the tab stops to be used.}
  41.     TabCt : Byte;           {Number of tab stops specified or implied.}
  42.     IpFile, OpFile : file;
  43.     IpPtr, OpPtr : DataPtr; {Pointers to buffers for input and output files.}
  44.     IpNext, OpNext : Word;  {Offset of next byte in input and output buffers.}
  45.     IpRead, OpWritten : Word; {Actual bytes read/written by each I/O request.}
  46.     MoreData : Boolean;      {Set to False at end of input file.}
  47.     Column : Word;          {Current column in current output line.}
  48.     FillCt : Word;          {Spaces required to fill out tab.}
  49.  
  50.  
  51. {----------------------- function GotFiles ---------------------------------}
  52.  
  53.  {Function GotFiles returns the value True if it successfully opens both the
  54.  input and output files.  Otherwise it returns False.}
  55.  
  56.   function GotFiles(var IpFile, OpFile : file) : Boolean;
  57.     var
  58.       HoldIOResult : Word;
  59.  
  60.     begin
  61.  
  62.  {Must specify two or more parameters including input and output files.}
  63.       if ParamCount < 2 then
  64.         begin
  65.           Writeln('Must specify an input file and an output file.');
  66.           GotFiles := False;
  67.           exit
  68.         end;
  69.  
  70.  {Setting FileMode=0 tells the Reset procedure to open file as read only.}
  71.       FileMode := 0;
  72.  
  73.       Assign(IpFile, ParamStr(1));
  74.       Assign(OpFile, ParamStr(2));
  75.  
  76.  {If Reset fails, display error message and set function result to False.}
  77.       Reset(IpFile, 1);
  78.       HoldIOResult := IOResult;
  79.       if HoldIOResult > 0 then
  80.         begin
  81.           case HoldIOResult of
  82.             2 :   Writeln('Input file not found: ', ParamStr(1));
  83.             3 :   Writeln('Invalid input file spec: ', ParamStr(1));
  84.             else  Writeln('Unable to open input file: ', ParamStr(1));
  85.           end;
  86.           GotFiles := False;
  87.           Exit
  88.         end;
  89.  
  90.  {If Rewrite fails, display error message and set function result to False.}
  91.       Rewrite(OpFile, 1);
  92.       HoldIOResult := IOResult;
  93.       if HoldIOResult > 0 then
  94.         begin
  95.           case HoldIOResult of
  96.             3 :   Writeln('Invalid output file spec: ', ParamStr(2));
  97.             else  Writeln('Unable to open output file: ', ParamStr(2));
  98.           end;
  99.           GotFiles := False;
  100.           Exit
  101.         end;
  102.  
  103.  {If both files opened successfully, return function result True.}
  104.       GotFiles := True
  105.  
  106.     end;
  107.  
  108.  
  109.  {------------------- procedure CloseDelete --------------------------------}
  110.  
  111.   procedure CloseDelete;
  112.     begin
  113.       Close(IpFile);
  114.       Close(OpFile);
  115.       Erase(OpFile)
  116.     end;
  117.  
  118.  
  119.  {--------------------- function GotTabs -----------------------------------}
  120.  
  121.  {Function GotTabs returns the value True if it successfully creates the
  122.    array of tab stops.  Otherwise it returns False.}
  123.  
  124.   function GotTabs(var Tab : TabArray; var TabCt : Byte) : Boolean;
  125.  
  126.     var
  127.       Temp : LongInt;
  128.       Code : Integer;
  129.       Start, I : Byte;
  130.     begin
  131.  
  132.  
  133.  {The default tab stops are at columns 1, 9, 17, 25 (and so on at intervals
  134.   of eight columns).  Internally, RPTab represents these as 0, 8, 16, 24 etc.
  135.   Since the interval between the last two explicit tab stops is propagated to
  136.   subsequent tab stops, EXPTABS sets two tab stops at columns 0 and 8 in the
  137.   Tab array and sets TabCT = 2.  It also sets GotTabs to True on the
  138.   assumption that tab stops will be OK.}
  139.  
  140.       Tab[1] := 0;
  141.       Tab[2] := 8;
  142.       TabCt  := 2;
  143.       GotTabs := True;
  144.  
  145.  
  146.  {If ParamCount is 2 then only files were specified and no tab stops.  Thus,
  147.   RPTAB sticks with the default tab stops set above.}
  148.  
  149.       if ParamCount = 2 then Exit;
  150.  
  151.  
  152.  {If the first specified tab stop (ParamStr(3)) is a valid integer and equals
  153.   1, then having already set the first tab stop at 1, we will start with the
  154.   4th parameter.}
  155.  
  156.       Val(ParamStr(3), Temp, Code);
  157.       if (Code = 0) and (Temp = 1) then
  158.         if ParamCount > 3
  159.           then Start := 4
  160.           else Exit
  161.       else Start := 3;
  162.       TabCt := ParamCount - Start + 2;
  163.  
  164.  
  165.  {Get each tab stop in turn.  Check that it is an integer between 1 and
  166.   65535 and that it is greater than the previous tab stop.  If not, display
  167.   an error message and return with GotTabs = False.}
  168.  {If a tab stop is OK, decrement it by 1 and store it in the corresponding
  169.   Tab array bucket.  I decrement it because internally I count columns
  170.   starting with zero while externally I count them starting with one.}
  171.  
  172.       for I := 2 to TabCt do
  173.         begin
  174.           Val(ParamStr(Start + I - 2), Temp, Code);
  175.           if (Code <> 0) or (Temp < 1) or (Temp > 65535) then
  176.             begin
  177.               Writeln('Tab stop must be integer between 1 and 65535: ',
  178.                       ParamStr(Start + I - 2));
  179.               GotTabs := False;
  180.               CloseDelete;
  181.               Exit
  182.             end;
  183.           if Tab[I - 1] >= (Temp - 1) then
  184.             begin
  185.               Writeln('Tab stop at ', Temp, ' must exceed the ',
  186.                       'previous tab stop at ', Tab[I - 1]+1, '.');
  187.               GotTabs := False;
  188.               CloseDelete;
  189.               Exit
  190.             end;
  191.           Tab[I] := Temp - 1
  192.         end
  193.     end;
  194.  
  195.  
  196.  {-------------------- function  ReadOk ------------------------------------}
  197.  
  198.  {Function ReadOk returns the value True if it successfully reads from the
  199.   input file.  Otherwise it displays an error message and returns False.}
  200.  
  201.   function ReadOK(var IpFile : file; var Buff : DataArray; BuffSize : Word;
  202.                   var IpRead : Word) : Boolean;
  203.     var
  204.       HoldIOResult : Word;
  205.     begin
  206.       BlockRead(IpFile, Buff, BuffSize, IpRead);
  207.       HoldIOResult := IOResult;
  208.       if HoldIOResult <> 0 then
  209.         begin
  210.           Writeln('Error reading input file.');
  211.           ReadOK := False;
  212.           CloseDelete
  213.         end
  214.       else ReadOK := True
  215.     end;
  216.  
  217.  
  218.  {---------------------- function WriteOK ----------------------------------}
  219.  
  220.  {Function WriteOk returns the value True if it successfully writes to the
  221.   output file.  Otherwise it displays an error message and returns False.}
  222.  
  223.   function WriteOK(var OpFile : file; var Buff : DataArray; WriteLen : Word;
  224.                    var OpWritten : Word) : Boolean;
  225.     var
  226.       HoldIOResult : Word;
  227.     begin
  228.       WriteOK := True;
  229.       BlockWrite(OpFile, Buff, WriteLen, OpWritten);
  230.       HoldIOResult := IOResult;
  231.       if HoldIOResult <> 0 then
  232.         begin
  233.           Writeln('Error writing output file.');
  234.           CloseDelete;
  235.           WriteOk := False
  236.         end;
  237.       if OpWritten <> WriteLen then
  238.         begin
  239.           Writeln('Ran out of space on disk writing output file.');
  240.           CloseDelete;
  241.           WriteOk := False
  242.         end;
  243.     end;
  244.  
  245.  
  246.  {-------------------- procedure ExpandTabs --------------------------------}
  247.  
  248.  {The ExpandTabs procedure is really the guts of the program.  I coded it in
  249.   assembly language for efficiency.  It scans the data in the input buffer
  250.   and copies it to the output buffer expanding tabs as necessary.  It
  251.   continues until it has filled up the output buffer or used the entire input
  252.   buffer.}
  253.  
  254.   {It returns values in the four var parameters as follows:
  255.  
  256.     IpNext : The offset of the next available character in the input buffer.
  257.              This will either be one byte beyond the end of the buffer
  258.              implying that the entire input buffer was used or it will be
  259.              somewhere in the middle of the buffer and thus will be the first
  260.              byte to be processed the next time ExpandTabs is called.
  261.  
  262.     OpNext : The offset of the next available byte in the output buffer. This
  263.              will either be one byte beyond the end of the buffer implying
  264.              that the entire output buffer has been filled or it will be
  265.              somewhere in the middle of the buffer and thus will be the first
  266.              byte to be filled the next time ExpandTabs is called.
  267.  
  268.     Column : The last line moved to the output buffer will often be
  269.              incomplete and will have to be finished the next time ExpandTabs
  270.              is called.  Column is the offset, within that line, of the next
  271.              character to be moved to it.  ExpandTabs will need this the next
  272.              time around in order to correctly expand any tabs that occur
  273.              later in the line.  Note that Column reflects the expansion of
  274.              any earlier tabs in the line.
  275.  
  276.     FillCt:  Sometimes a tab will be found in the input buffer when there is
  277.              very little room left in the output buffer.  If the tab expands
  278.              to more spaces than can be accomodated in the remainder of the\
  279.              output buffer,  the number of additional spaces required will be
  280.              returned in FillCt.  Otherwise it will be zero.}
  281.  
  282.   procedure ExpandTabs(IpPtr, OpPtr : DataPtr; var IpNext, OpNext : Word;
  283.                        IpLen, OpLen : Word; TabCt : Byte; Tab : TabArray;
  284.                        var Column, FillCt : Word);
  285.     begin
  286.       asm
  287.         cld
  288.         push ds
  289.  
  290.         les   bx,FillCt     {Address of FillCt.}
  291.         mov   cx,es:[bx]    {Value of FillCt.  If FillCt zero, then didn't}
  292.         jcxz @GetCol        {have unfinished tab at end of last op buffer.}
  293.         cmp   cx,OpLen      {If FillCt less than or equal OpLen.}
  294.         jbe  @FinTab        {then fill with spaces for rest of tab.}
  295.         mov   cx,OpLen      {Value of OpLen.}
  296.         sub   es:[bx],cx    {Subtract fill length from FillCt.}
  297.         les   bx,Column     {Address of Column.}
  298.         add   es:[bx],cx    {Add fill length to Column.}
  299.         les   di,OpPtr      {Points to output  buffer.}
  300.         lds   bx,OpNext     {Address of OpNext.}
  301.         add   di,ds:[bx]    {Offset of next byte in output buffer.}
  302.         add   ds:[bx],cx    {Add fill length to OpNext.}
  303.         mov   al,20h
  304.         rep   stosb         {Fill with spaces.}
  305.         jmp  @Finished
  306.  
  307.       @FinTab:
  308.         dec   IpLen         {Decrement Iplen because tab now used.}
  309.         mov   es:word ptr[bx],0 {Set FillCt to zero.}
  310.         les   bx,IpNext
  311.         inc   es:word ptr[bx] {Increment IpNext pointer past the tab.}
  312.         les   bx,Column     {Address of Column.}
  313.         add   es:[bx],cx    {Add fill length to Column.}
  314.         les   di,OpPtr      {Points to output  buffer.}
  315.         lds   bx,OpNext     {Address of OpNext.}
  316.         add   di,ds:[bx]    {Offset of next byte in output buffer.}
  317.         add   ds:[bx],cx    {Add fill length to OpNext.}
  318.         sub   OpLen,cx      {Reduce OpLen by length of fill.}
  319.         mov   al,20h
  320.         rep   stosb         {Fill with spaces.}
  321.         jz   @Finished      {Check zero flag from sub Oplen,cx.}
  322.         or    bx,bx
  323.         jz   @Finished      {Finished if IpLen = 0.}
  324.  
  325.       @GetCol:
  326.         les   bx,Column     {Address of Column.}
  327.         mov   cx,es:[bx]    {Value of Column.}
  328.         lds   si,IpPtr      {Points to input  buffer.}
  329.         les   bx,IpNext     {Address of IpNext.}
  330.         add   si,es:[bx]    {Offset of next byte in input buffer.}
  331.         les   bx,OpNext     {Address of OpNext.}
  332.         mov   ax,es:[bx]    {Value of OpNext.}
  333.         les   di,OpPtr      {Points to output buffer.}
  334.         add   di,ax         {Offset of next byte in output buffer.}
  335.         mov   bx,IpLen      {Length of data in input buffer.}
  336.         mov   dx,OpLen      {Available space in output buffer.}
  337.         mov   ah,TabCt      {Number of specified tab stops.}
  338.         push  bp            {Save stack frame pointer.}
  339.         lea   bp,Tab        {Offset in SS of Tab array.}
  340.  
  341.       @NextByte:
  342.         lodsb               {Get next input byte.}
  343.         cmp  al,0dh
  344.         jbe  @IsItCR
  345.       @DoReg:              {If above CR (0dh) it is a regular character.}
  346.         inc  cx            {Increment Column.}
  347.       @StoreOP:
  348.         stosb              {Store character in output buffer.}
  349.         dec  bx            {Decrement IpLen.}
  350.         jz  @FinishUp      {We are done if IpLen is used up.}
  351.         dec  dx            {Decrement OpLen.}
  352.         jz  @FinishUp      {We are done if OpLen is used up.}
  353.         jmp @NextByte      {Go and get next byte.}
  354.       @IsItCr:
  355.         jnz @IsItLF
  356.         mov  cx,0          {Set Column = 0 when we find CR.}
  357.         jmp @StoreOp
  358.       @IsItLF:
  359.         cmp  al,0ah
  360.         jz  @StoreOp       {If LF, then don't change Column.}
  361.       @IsItTab:
  362.         cmp  al,09h
  363.         jnz  @DoReg        {If not CR, LF or Tab it is a regular character.}
  364.  
  365.         push ax            {Save TabCt.}
  366.         push di            {Save offset of next op byte.}
  367.         mov  di,-2         {Index for tab array search.}
  368.       @ScanTabs:
  369.         inc  di
  370.         inc  di            {Point to next tab stop in Tab array.}
  371.         cmp  cx,[bp+di]    {Compare Column to tab stop.}
  372.         jb   @FoundTab     {The first tab stop greater than Column is the}
  373.                            {tab stop we want to space out to.}
  374.         dec  ah            {Decrement TabCt.}
  375.         jnz  @ScanTabs     {If more tabs in table, continue scan.}
  376.  
  377.  {Column is beyond the last tab in the Tab array, so we must propagate the
  378.   interval between the last two explicit tab stops to find the tab stop to
  379.   space out to.  To do this we compute:
  380.  
  381.   1. Column MINUS NextToLastTabStop
  382.   2. LastTabStop MINUS NextToLastTabStop
  383.   3. The result of line 1 MOD the result of line 2
  384.   4. The result of line 2 MINUS the result of line 3
  385.  
  386.   If the interval from NextToLastStop to Column (line 1) was an exact
  387.   multiple of the interval from the NextToLastTabStop to the LastTabStop
  388.   (line 2) then clearly Column would fall on one of the propagated tab stops.
  389.   In this case we would want to tab to the next tab stop or the full interval
  390.   between two tab stops.  Since the MOD (line3) would be zero, in this case,
  391.   line 4 will produce the correct result for the number of spaces.  In any
  392.   other case, the MOD will not be zero and we will tab less than the full
  393.   interval to the next tab stop as we should.}
  394.  
  395.         push dx            {Save OpLen.}
  396.         mov  ax,[bp+di-2]  {Next to last tab stop in Tab array.}
  397.         mov  di,[bp+di]    {Last tab stop in Tab array.}
  398.         sub  di,ax         {Difference between last two tab stops.}
  399.         sub  ax,cx         {Next to last tab stop - Column.}
  400.         neg  ax            {Column - next to last tab stop.}
  401.         xor  dx,dx         {High word of zero.}
  402.         div  di            {dx=((Column-NextLast) mod (Last-NextLast))}
  403.         sub  di,dx         {di = Number of spaces required for tab.}
  404.         mov  ax,di
  405.         pop  dx            {Retrieve OpLen.}
  406.         add  di,cx         {Add Column to number of spaces for tab.}
  407.         jnc @DoSpaces      {If no carry, then doesn't go beyond 65535.}
  408.         sub  ax,di         {Subtract length beyond 65535 from # of spaces.}
  409.         jmp @DoSpaces      {If ax=0 because cx=65535, @DoSpaces works right.}
  410.       @FoundTab:
  411.         mov  ax,[bp+di]    {Tab stop to space out to.}
  412.         sub  ax,cx         {Spaces required = tab stop - Column.}
  413.  
  414.       @DoSpaces:
  415.         pop  di            {Restore offset of next output byte.}
  416.         cmp  ax,dx         {Compare spaces required to OpLen.}
  417.         ja  @SpaceBeyond
  418.         xchg ax,cx         {ax = Column, cx = spaces required.}
  419.         add  ax,cx         {ax = adjusted Column.}
  420.         sub  dx,cx         {dx = adjusted OpLen.}
  421.         push ax            {Save Column.}
  422.         mov  al,20h
  423.         rep  stosb         {Store spaces.}
  424.         pop  cx            {Restore Column.}
  425.         pop  ax            {Restore TabCt.}
  426.         jz  @FinishUp      {Jump if OpLen reduced to zero.}
  427.         dec  bx            {Decrement IpLen.}
  428.         jz  @FinishUp      {We are done if IpLen is used up.}
  429.         jmp @NextByte      {Else go and get next ip byte.}
  430.  
  431.  
  432.  {This routine is executed if the number of spaces for the tab would carry
  433.   beyond the end of the output buffer.  In this case, I fill as many spaces
  434.   as possible and then set FillCt to the number of spaces needed to finish
  435.   the tab before returning.}
  436.  
  437.       @SpaceBeyond:
  438.         dec  si            {Point back to tab.}
  439.         sub  ax,dx         {Value for FillCt.}
  440.         add  cx,dx         {Adjust Column for OpLen.}
  441.         push ax            {Save FillCt.}
  442.         push cx            {Save Column.}
  443.         mov  cx,dx         {cx = OpLen.}
  444.         mov  al,20h
  445.         rep  stosb         {Store spaces.}
  446.         pop  cx            {Restore Column.}
  447.         pop  dx            {Restore FillCt.}
  448.         pop  ax            {Restore TabCt.}
  449.         pop  bp            {Restore stack frame pointer.}
  450.         les  bx,FillCt
  451.         mov  es:[bx],dx    {Set FillCt to remaining spaces for tab.}
  452.         jmp @FinishUp1
  453.  
  454.       @FinishUp:
  455.         pop  bp            {Restore stack frame pointer}
  456.       @FinishUp1:
  457.         les  bx,Column
  458.         mov  es:[bx],cx    {Update Column}
  459.       @FinishUp2:
  460.         les  bx,IpPtr      {Points to input buffer}
  461.         sub  si,bx         {New value of IpNext}
  462.         les  bx,IpNext     {Address of IpNext}
  463.         mov  es:[bx],si    {Update IpNext.}
  464.         les  bx,OpPtr      {Points to output buffer}
  465.         sub  di,bx         {New value of OpNext}
  466.         les  bx,OpNext     {Address of OpNext}
  467.         mov  es:[bx],di
  468.       @Finished:
  469.         pop  ds
  470.       end
  471.     end;
  472.  
  473.  
  474.  {------------------- Main program block -----------------------------------}
  475.  
  476.   begin
  477.     Writeln; {Leave a blank line before completion or error message}
  478.  
  479.  
  480.  {If unable to open the files or to create the table of tab stops, I halt
  481.   since the error message would have been displayed by the called routine.}
  482.  
  483.     if not GotFiles(IpFile, OpFile) then Halt;
  484.     if not GotTabs(Tab, Tabct) then Halt;
  485.  
  486.     New(IpPtr); {Get 32K buffers for input and output. Reading and writing}
  487.     New(OpPtr); {32K at a time is more efficient than a line at a time.}
  488.  
  489.     OpNext := 0; {Start at position zero of output buffer.}
  490.     Column := 0; {Start at position zero of the first line.}
  491.     FillCT := 0; {Indicate no tab to be finished from previous time.}
  492.  
  493.     repeat {Repeat until entire input file has been read and processed.}
  494.  
  495.       IpNext := 0; {Reading new input, so start position in buffer is zero.}
  496.  
  497.  
  498.       {Read 32K (BuffSize) into the input buffer.  If read is nogood, halt.}
  499.       if not ReadOK(IpFile, IpPtr^, BuffSize, IpRead) then Halt;
  500.  
  501.       {If read full buffer then MoreData is True, else False.}
  502.       MoreData := IpRead = BuffSize;
  503.  
  504.  
  505.       repeat {Repeat until all data in the input buffer has been copied to
  506.               the output buffer with tabs expanded.}
  507.  
  508.        {ExpandTabs copies input data to output buffer with tabs expanded
  509.         until output buffer is full or entire input buffer has been used.}
  510.  
  511.         ExpandTabs(IpPtr, OpPtr, IpNext, OpNext, IpRead-IpNext,
  512.                    BuffSize-OpNext, TabCt, Tab, Column, FillCt);
  513.  
  514.         {If output buffer full, write it to the output file.}
  515.         if OpNext = BuffSize then
  516.           begin
  517.             if not WriteOK(OpFile, OpPtr^, BuffSize, OpWritten) then Halt;
  518.             OpNext := 0
  519.           end
  520.  
  521.       until IpNext = IpRead;
  522.  
  523.     until not MoreData;
  524.  
  525.     {If have partial unwritten output buffer, at end, then write it.}
  526.     if OpNext <> 0 then
  527.       if not WriteOK(OpFile, OpPtr^, OpNext, OpWritten) then Halt;
  528.  
  529.     Close(IpFile);
  530.     Close(OpFile);
  531.     Writeln('Tab expansion completed.')
  532.   end.
  533.